/*
 *  PCL by Davide Libenzi ( Portable Coroutine Library )
 *  Copyright (C) 2003  Davide Libenzi
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  Davide Libenzi <davidel@xmailserver.org>
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include "pcl_config.h"
#include "pcl.h"

#if defined(CO_USE_FIBERS)
#define _WIN32_WINNT 0x0400
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

typedef void* co_core_ctx_t;

typedef size_t integerptr_t;

#ifdef _MSC_VER
#pragma comment(lib, "kernel32.lib")
#endif

#elif defined(CO_USE_UCONEXT)
#include <ucontext.h>

typedef ucontext_t co_core_ctx_t;
#else
#include <setjmp.h>

typedef jmp_buf co_core_ctx_t;
#endif

#if defined(CO_USE_SIGCONTEXT)
#include <signal.h>
#endif


/*
 * The following value must be power of two ( N^2 ).
 */
#define CO_STK_ALIGN 256
#define CO_STK_COROSIZE ((sizeof(coroutine) + CO_STK_ALIGN - 1) & ~(CO_STK_ALIGN - 1))
#define CO_MIN_SIZE (4 * 1024)


typedef struct s_co_ctx {
	co_core_ctx_t cc;
} co_ctx_t;

typedef struct s_coroutine {
	co_ctx_t ctx;
#if !defined(CO_USE_FIBERS)
	int alloc;
#endif
	struct s_coroutine *caller;
	struct s_coroutine *restarget;
	void (*func)(void *);
	void *data;
} coroutine;


static coroutine co_main;

static coroutine *co_curr = &co_main;

static coroutine *co_dhelper;

#if defined(CO_USE_SIGCONTEXT)

static volatile int ctx_called;
static co_ctx_t *ctx_creating;
static void *ctx_creating_func;
static sigset_t ctx_creating_sigs;
static co_ctx_t ctx_trampoline;
static co_ctx_t ctx_caller;

#endif /* #if defined(CO_USE_SIGCONTEXT) */



static int co_ctx_sdir(integerptr_t psp) {
	int nav = 0;
	integerptr_t csp = (integerptr_t) &nav;

	return psp > csp ? -1: +1;
}


static int co_ctx_stackdir(void) {
	int cav = 0;

	return co_ctx_sdir((integerptr_t) &cav);
}


#if defined(CO_USE_FIBERS)
static void co_switch_context(co_ctx_t *octx, co_ctx_t *nctx) {

/*
	if ((LPVOID) 0x1e00 == GetCurrentFiber())
	{
		co_main.ctx.cc = ConvertThreadToFiber(0);
	}
	*/
	if (!co_main.ctx.cc)
	{
		co_main.ctx.cc = ConvertThreadToFiber(0);
	}
	
	SwitchToFiber(nctx->cc);
}

#elif defined(CO_USE_UCONEXT)

static int co_set_context(co_ctx_t *ctx, void *func, char *stkbase, long stksiz) {

	if (getcontext(&ctx->cc))
		return -1;
 
	ctx->cc.uc_link = NULL;
 
	ctx->cc.uc_stack.ss_sp = stkbase;
	ctx->cc.uc_stack.ss_size = stksiz - sizeof(long);
	ctx->cc.uc_stack.ss_flags = 0;
 
	makecontext(&ctx->cc, func, 1);

	return 0;
}


static void co_switch_context(co_ctx_t *octx, co_ctx_t *nctx) {

	if (swapcontext(&octx->cc, &nctx->cc) < 0) {
		fprintf(stderr, "[PCL] Context switch failed: curr=%p\n",
			co_curr);
		exit(1);
	}
}

#else /* #if defined(CO_USE_UCONEXT) */

#if defined(CO_USE_SIGCONTEXT)

/*
 * This code comes from the GNU Pth implementation and uses the
 * sigstack/sigaltstack() trick.
 *
 * The ingenious fact is that this variant runs really on _all_ POSIX
 * compliant systems without special platform kludges.  But be _VERY_
 * carefully when you change something in the following code. The slightest
 * change or reordering can lead to horribly broken code.  Really every
 * function call in the following case is intended to be how it is, doubt
 * me...
 *
 * For more details we strongly recommend you to read the companion
 * paper ``Portable Multithreading -- The Signal Stack Trick for
 * User-Space Thread Creation'' from Ralf S. Engelschall.
 */

static void co_ctx_bootstrap(void) {
	co_ctx_t * volatile ctx_starting;
	void (* volatile ctx_starting_func)(void);
 
	/*
	 * Switch to the final signal mask (inherited from parent)
	 */
	sigprocmask(SIG_SETMASK, &ctx_creating_sigs, NULL);
 
	/*
	 * Move startup details from static storage to local auto
	 * variables which is necessary because it has to survive in
	 * a local context until the thread is scheduled for real.
	 */
	ctx_starting = ctx_creating;
	ctx_starting_func = (void (*)(void)) ctx_creating_func;
 
	/*
	 * Save current machine state (on new stack) and
	 * go back to caller until we're scheduled for real...
	 */
	if (!setjmp(ctx_starting->cc))
		longjmp(ctx_caller.cc, 1);

	/*
	 * The new thread is now running: GREAT!
	 * Now we just invoke its init function....
	 */
	ctx_starting_func();

	fprintf(stderr, "[PCL] Hmm, you really shouldn't reach this point: curr=%p\n",
		co_curr);
	exit(1);
}


static void co_ctx_trampoline(int sig) {
	/*
	 * Save current machine state and _immediately_ go back with
	 * a standard "return" (to stop the signal handler situation)
	 * to let him remove the stack again. Notice that we really
	 * have do a normal "return" here, or the OS would consider
	 * the thread to be running on a signal stack which isn't
	 * good (for instance it wouldn't allow us to spawn a thread
	 * from within a thread, etc.)
	 */
	if (!setjmp(ctx_trampoline.cc)) {
		ctx_called = 1;
		return;
	}
 
	/*
	 * Ok, the caller has longjmp'ed back to us, so now prepare
	 * us for the real machine state switching. We have to jump
	 * into another function here to get a new stack context for
	 * the auto variables (which have to be auto-variables
	 * because the start of the thread happens later).
	 */
	co_ctx_bootstrap();
}


static int co_set_context(co_ctx_t *ctx, void *func, char *stkbase, long stksiz) {
	struct sigaction sa;
	struct sigaction osa;
	sigset_t osigs;
	sigset_t sigs;
#if defined(CO_HAS_SIGSTACK)
	struct sigstack ss;
	struct sigstack oss;
#elif defined(CO_HAS_SIGALTSTACK)
	struct sigaltstack ss;
	struct sigaltstack oss;
#else
#error "PCL: Unknown context stack type"
#endif

	/*
	 * Preserve the SIGUSR1 signal state, block SIGUSR1,
	 * and establish our signal handler. The signal will
	 * later transfer control onto the signal stack.
	 */
	sigemptyset(&sigs);
	sigaddset(&sigs, SIGUSR1);
	sigprocmask(SIG_BLOCK, &sigs, &osigs);
	sa.sa_handler = co_ctx_trampoline;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_ONSTACK;
	if (sigaction(SIGUSR1, &sa, &osa) != 0)
		return -1;

	/*
	 * Set the new stack.
	 *
	 * For sigaltstack we're lucky [from sigaltstack(2) on
	 * FreeBSD 3.1]: ``Signal stacks are automatically adjusted
	 * for the direction of stack growth and alignment
	 * requirements''
	 *
	 * For sigstack we have to decide ourself [from sigstack(2)
	 * on Solaris 2.6]: ``The direction of stack growth is not
	 * indicated in the historical definition of struct sigstack.
	 * The only way to portably establish a stack pointer is for
	 * the application to determine stack growth direction.''
	 */
#if defined(CO_HAS_SIGALTSTACK)
	ss.ss_sp = stkbase;
	ss.ss_size = stksiz - sizeof(long);
	ss.ss_flags = 0;
	if (sigaltstack(&ss, &oss) < 0)
		return -1;
#elif defined(CO_HAS_SIGSTACK)
	if (co_ctx_stackdir() < 0)
		ss.ss_sp = (stkbase + stksiz - sizeof(long));
	else
		ss.ss_sp = stkbase;
	ss.ss_onstack = 0;
	if (sigstack(&ss, &oss) < 0)
		return -1;
#else
#error "PCL: Unknown context stack type"
#endif

	/*
	 * Now transfer control onto the signal stack and set it up.
	 * It will return immediately via "return" after the setjmp()
	 * was performed. Be careful here with race conditions.  The
	 * signal can be delivered the first time sigsuspend() is
	 * called.
	 */
	ctx_called = 0;
	kill(getpid(), SIGUSR1);
	sigfillset(&sigs);
	sigdelset(&sigs, SIGUSR1);
	while (!ctx_called)
		sigsuspend(&sigs);

	/*
	 * Inform the system that we are back off the signal stack by
	 * removing the alternative signal stack. Be careful here: It
	 * first has to be disabled, before it can be removed.
	 */
#if defined(CO_HAS_SIGALTSTACK)
	sigaltstack(NULL, &ss);
	ss.ss_flags = SS_DISABLE;
	if (sigaltstack(&ss, NULL) < 0)
		return -1;
	sigaltstack(NULL, &ss);
	if (!(ss.ss_flags & SS_DISABLE))
		return -1;
	if (!(oss.ss_flags & SS_DISABLE))
		sigaltstack(&oss, NULL);
#elif defined(CO_HAS_SIGSTACK)
	if (sigstack(&oss, NULL))
		return -1;
#else
#error "PCL: Unknown context stack type"
#endif

	/*
	 * Restore the old SIGUSR1 signal handler and mask
	 */
	sigaction(SIGUSR1, &osa, NULL);
	sigprocmask(SIG_SETMASK, &osigs, NULL);

	/*
	 * Set creation information.
	 */
	ctx_creating = ctx;
	ctx_creating_func = func;
	memcpy(&ctx_creating_sigs, &osigs, sizeof(sigset_t));

	/*
	 * Now enter the trampoline again, but this time not as a signal
	 * handler. Instead we jump into it directly.
	 */
	if (!setjmp(ctx_caller.cc))
		longjmp(ctx_trampoline.cc, 1);

	return 0;
}

#else /* #if defined(CO_USE_SIGCONTEXT) */

static int co_set_context(co_ctx_t *ctx, void *func, char *stkbase, long stksiz) {
	char *stack;

	stack = stkbase + stksiz - sizeof(long);

	setjmp(ctx->cc);

#if defined(__GLIBC__) && defined(__GLIBC_MINOR__) \
    && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 0 && defined(JB_PC) && defined(JB_SP)
	ctx->cc[0].__jmpbuf[JB_PC] = (int) func;
	ctx->cc[0].__jmpbuf[JB_SP] = (int) stack;
#elif defined(__GLIBC__) && defined(__GLIBC_MINOR__) \
    && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 0 && defined(__mc68000__)
	ctx->cc[0].__jmpbuf[0].__aregs[0] = (long) func;
	ctx->cc[0].__jmpbuf[0].__sp = (int *) stack;
#elif defined(__GNU_LIBRARY__) && defined(__i386__)
	ctx->cc[0].__jmpbuf[0].__pc = func;
	ctx->cc[0].__jmpbuf[0].__sp = stack;
#elif defined(_WIN32) && defined(_MSC_VER)
	((_JUMP_BUFFER *) &ctx->cc)->Eip = (long) func;
	((_JUMP_BUFFER *) &ctx->cc)->Esp = (long) stack;
#elif defined(__GLIBC__) && defined(__GLIBC_MINOR__) \
    && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 0 && (defined(__powerpc64__) || defined(__powerpc__))
	ctx->cc[0].__jmpbuf[JB_LR] = (int) func;
	ctx->cc[0].__jmpbuf[JB_GPR1] = (int) stack;
#else
#error "PCL: Unsupported setjmp/longjmp platform. Please report to <davidel@xmailserver.org>"
#endif

	return 0;
}

#endif /* #if defined(CO_USE_SIGCONTEXT) */


static void co_switch_context(co_ctx_t *octx, co_ctx_t *nctx) {

	if (!setjmp(octx->cc))
		longjmp(nctx->cc, 1);
}

#endif /* #if defined(CO_USE_UCONEXT) */

#if defined(CO_USE_FIBERS)

static VOID CALLBACK co_fiber_runner(PVOID lpParameter) {
	coroutine *co = co_curr;
	co->restarget = co->caller;
	co->func(co->data);
	co_exit();
}
#else
static void co_runner(void) {
	coroutine *co = co_curr;

	co->restarget = co->caller;
	co->func(co->data);
	co_exit();
}
#endif

coroutine_t co_create(void (*func)(void *), void *data, void *stack, int size) {
#if !defined(CO_USE_FIBERS)
	int alloc = 0;
#endif
	coroutine *co;

#if defined(CO_USE_FIBERS)
	if ((size &= ~(sizeof(long) - 1)) < CO_MIN_SIZE) {
		size = CO_MIN_SIZE;
	}
	
	co = malloc(sizeof(coroutine));
	co->func = func;
	co->data = data;
	
	co->ctx.cc = CreateFiber(size, co_fiber_runner, 0);
	if (!co->ctx.cc) {
		return NULL;
	}
#else
	if ((size &= ~(sizeof(long) - 1)) < CO_MIN_SIZE) {
		if (stack)
			return NULL;
		else
			size = CO_MIN_SIZE;
	}
	
	if (!stack) {
		size = (size + sizeof(coroutine) + CO_STK_ALIGN - 1) & ~(CO_STK_ALIGN - 1);
		stack = malloc(size);
		if (!stack)
			return NULL;
		alloc = size;
	}
	co = stack;
	stack = (char *) stack + CO_STK_COROSIZE;
	co->alloc = alloc;
	co->func = func;
	co->data = data;
	if (co_set_context(&co->ctx, co_runner, stack, size - CO_STK_COROSIZE) < 0) {
		if (alloc)
			free(co);
		return NULL;
	}
#endif

	return (coroutine_t) co;
}


void co_delete(coroutine_t coro) {
	coroutine *co = (coroutine *) coro;
	
	if (co == co_curr) {
		fprintf(stderr, "[PCL] Cannot delete itself: curr=%p\n",
			co_curr);
		exit(1);
	}

#if defined(CO_USE_FIBERS)
	DeleteFiber(co->ctx.cc);
	free(co);
#else
	if (co->alloc)
		free(co);
#endif
}


void co_call(coroutine_t coro) {
	coroutine *co = (coroutine *) coro, *oldco = co_curr;

	co->caller = co_curr;
	co_curr = co;

	co_switch_context(&oldco->ctx, &co->ctx);
}


void co_resume(void) {

	co_call(co_curr->restarget);
	co_curr->restarget = co_curr->caller;
}


static void co_del_helper(void *data) {
	coroutine *cdh;
	
	for (;;) {
		cdh = co_dhelper;
		co_dhelper = NULL;
		co_delete(co_curr->caller);
		co_call((coroutine_t) cdh);
		if (!co_dhelper) {
			fprintf(stderr, "[PCL] Resume to delete helper coroutine: curr=%p\n",
				co_curr);
			exit(1);
		}
	}
}


void co_exit_to(coroutine_t coro) {
	coroutine *co = (coroutine *) coro;
	static coroutine *dchelper = NULL;
	static char stk[CO_MIN_SIZE];

	if (!dchelper &&
	    !(dchelper = co_create(co_del_helper, NULL, stk, sizeof(stk)))) {
	    
		fprintf(stderr, "[PCL] Unable to create delete helper coroutine: curr=%p\n",
			co_curr);
		exit(1);
	}

	co_dhelper = co;
 
	co_call((coroutine_t) dchelper);

	fprintf(stderr, "[PCL] Stale coroutine called: curr=%p\n",
		co_curr);
	exit(1);
}


void co_exit(void) {

	co_exit_to((coroutine_t) co_curr->restarget);
}


coroutine_t co_current(void) {

	return (coroutine_t) co_curr;
}

